/**
 * @file Panel.c
 * @brief Routines associated with the side panel (manipulation of the
 *  panel itself or functions activated by the panel).
 * @author Matt Stallmann @date 29 Dec 2001
 * $Id: Panel.c 10 2007-06-28 20:08:57Z mfms $
 *
 *  In each case, the global variable eve is the current event being handled
 * (typically mouse or keyboard).  All need to return a bool to indicate
 * whether they caused an exit event.
 */


/*
 *  Copyright (C) 1989, 2001, 2007  Matthias Stallmann, Prashant Hebbar.
 *  Contact: matt_stallmann@ncsu.edu
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program (file COPYING.txt); if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
 *  USA.
 */

#include "All.h"

/* globals for this module only */
static Window  pane_w;
static char    *p_top[PANEL_TOPICS + 1];

/** macro for highlighting a panel */
#define HILI_PANE(y) XFillRectangle(disp, pane_w , rubber_gc, 1,\
				    PANEL_VPAD + (y) * Y_P + 1, PANEL_WIDTH - 2, Y_P - 2);

/* Local functions (one for each panel rectangle) ****************/


/**
		Creates vertices on left mouse button clicks.

                FUNCTIONS CALLED:
			add_vertex().
*/
static bool creat_vx(void) {
  char dummy[LINE_LENGTH];	/* needed for query call, but ignored */
  int r_val;

  /* the left mouse button */
  if (eve.BUT == LEFT_BUTTON) {
    /* adjust it to ORIGIN from the visible relative */	
    /* check if vertex already there */
    if (get_vx_id(eve.XC, eve.YC) != NULL_VERTEX) {
		BEEP;
      r_val = query(eve.XC, eve.YC,
                    "Vertex collision: Hit <RET> to continue",
                    dummy);
		return false;
    }
    add_vertex(eve.XC, eve.YC, "");
  }
  else {
    BEEP;
  }
  return false;
} /* creat_vx ends */

/**
		Creates edges between vertices after the vertices for the edge
		have been clicked using left mouse button.
*/
static bool creat_ej(void) {
  XEvent	ej_ev;
  char more = TRUE;
  int	cur_id, old_id;	/* the current & old vx ids */
  EJ_NODE	*creat_ej_rec();
  
  cur_id = get_vx_id(eve.XC,eve.YC);
  if ( (eve.BUT != LEFT_BUTTON) || (cur_id < 0) ) { /* wrong button or invalid
                                                       vertex */
    BEEP;
    return false;
  }		
  
  /* first click */
  old_id = cur_id;
  HILITE(cur_id);
  
  while (more) {
    XNextEvent(disp,&ej_ev);
    if ( handle_background_event(ej_ev) ) continue;
    if ( ej_ev.type == ButtonPress ) { /* choose only but-press ev */ 
      if ( (ej_ev.BUT == LEFT_BUTTON) && (ej_ev.WIN == main_canvas) ) {
        /* left mouse clicked in main window */
        cur_id = get_vx_id(ej_ev.XC,ej_ev.YC);
        if (cur_id >= 0) {  /* valid vx clicked on */
          /* acknowledge the edge creation */
          HILITE(old_id);
          add_edge(old_id,cur_id," ");
          more = FALSE;
        }
        else
          BEEP;
      }/* if Button = LEFT_BUTTON */
      else
        BEEP;
    } /* if button press */
  } /* while more */
  return false;
} /* create_ej ends */

/**
		Moves vertex, knot, or label on dragging the
		mouse with middle button pressed.
*/
static bool mov(void) {
  XEvent	l_ev;	/* the local event structure */
  int	more = TRUE;              /* more events to handle (while waiting for
                                 button release) */
  int type = 0;                 /* vertex (1), knot (2), or label (3)? */
  vertex	cur_id;                /* id of vertex, if it's a vertex */
  int	indx;                     /* which knot is it? */
  edge edj;                     /* pointer to the edge whose knot or label
                                   is selected */
  
  /* not relevant if not a left (mfms 2005/03/25) or middle button event */
  if ( eve.BUT != MIDDLE_BUTTON && eve.BUT != LEFT_BUTTON ) {
    BEEP;
    return false;
  }
  /* if in main_canvas then may be on a vertex ? */
  if ( eve.WIN == main_canvas ) {
    /* is it valid? */
    if ( is_valid_vertex(cur_id = get_vx_id(eve.XC,eve.YC)) )
      type = 1; /* its a vertex ! */
    else if ((edj = get_knot_at(eve.XC, eve.YC, &indx)) != NULL_EDGE)
      type = 2; /* it's an knot point !! */
    else {
      BEEP;
      return false;
    }
  } 	 /* in an edge label window ? */
  else if ( (edj = edge_from_window(eve.WIN)) == NULL) {  
    BEEP;
    return false;
  }
  /* ack. by cursor change: 'l_ev.WIN' can be 'main_canvas' or 'l_win' */
  XDefineCursor( disp, eve.WIN, mv_cur );
  /* get the new position */
  while( more ) {
    XNextEvent( disp, &l_ev );
    if ( handle_background_event( l_ev ) ) continue;

    /* choose only release ev */ 
    if ( l_ev.type == ButtonRelease ) {
      /* left (mfms 2005/03/25) or middle button , in main window ? */
      if( (l_ev.BUT==MIDDLE_BUTTON || l_ev.BUT == LEFT_BUTTON)
          && (l_ev.WIN==main_canvas) ) {
        if ( type == 1 ) { /* vx was dragged */
          move_vx_ej( cur_id, l_ev.XC, l_ev.YC );
          XDefineCursor( disp, eve.WIN, sel_curs );
        }
        else if ( type == 2 ) { /* knot was dragged */
          move_knot( edj, indx, l_ev.XC - KN(edj,indx,0),
                    l_ev.YC - KN(edj,indx,1) );
          XDefineCursor( disp, eve.WIN, sel_curs );
        }
        else { /* label was dragged */
          LX( edj ) = l_ev.XC;
          LY( edj ) = l_ev.YC;
          /* added centering to this (MS 3-2-92) */
          mov_elab_window( edj, LX( edj ), LY( edj ) );
          XDefineCursor( disp, eve.WIN, label_curs );
        }
        more = FALSE;
      }
      else {
        BEEP;
      }
    } /* if event is a button release */
  } /* while more */
  return false;
} /* mov ends */

/**
		Deletes a vx and its incident edges if a vx is selected
		else deletes the selected edge.
*/

static bool delet( void ) {
  int	cur_id;
  edge t_pnt;

  if( eve.BUT != LEFT_BUTTON ) { /* not the left button */
    BEEP;
    return false;
  }		
  /* Now, could this be a vertex ...? */
  cur_id = get_vx_id( eve.XC, eve.YC );
  if( cur_id >= 0 ) {
	 delete_vertex( cur_id );
    return false;
  }

  /* Maybe its an edge ! */
  t_pnt = get_ej_at_xy( eve.XC, eve.YC );	
  if( t_pnt != NULL_EDGE ) {
    delete_edge( t_pnt );
  }
  else {                        /* Not a vertex nor an edge !! */
    BEEP;
  }
  return false;
} /* delet ends */

/**
	Edits a vertex or edge label (mouse/keyboard inputs).
*/
static bool edit_label( void ) {
  bool more = true;
  char lookup_string[ KEY_PRESS_LENGTH + 1 ];
  char *label;
  int	i = 0;
  vertex	vx;
  KeySym	key;
  XEvent	l_ev;
  Window	wind;
  edge edj;
  bool vertx = false;
  int height;			/* height of window (based on font) */
  
  switch ( eve.type ) {
  case EnterNotify:
    /* edge label window ? */
    if( (edj = edge_from_window(eve.WIN)) != NULL_EDGE ) {
      label = LABEL( edj );
    }	 /* vertex label window ? */
    else if( (vx = vertex_from_window( eve.WIN) ) != NULL_VERTEX ) {
      label = VLABEL(vx);
      vertx = true;
    }
    else { /* not a label window */
      BEEP;
      return false;
    }
    /* same window in both cases ! */
    wind = eve.WIN;
    break;
  case ButtonPress:
    if( eve.BUT == LEFT_BUTTON ) { /* could be on an edge or vertex */
      if( (vx = get_vx_id( eve.XC, eve.YC )) != NULL_VERTEX ) {
        expose_vertex_label( vx );
        label = VLABEL( vx );
        wind = VWIN( vx );
        vertx = true;
      }
      else if( (edj = get_ej_at_xy( eve.XC, eve.YC )) != NULL_EDGE ) {
        expose_edge_label( edj );
        label = LABEL( edj );
        wind = LWIN( edj );
      }
      else {
        BEEP;
        return false;
      }
    } /* if eve.BUT == LEFT_BUTTON */
    else {	
      BEEP;
      return false;
    }
    break;
  default:
    BEEP;
    return false;
  } /* switch eve.type */
  
  XWarpPointer( disp, None, wind, 0, 0, 0, 0, 2, 2 );
  while( more ) {
    XNextEvent( disp, &l_ev );
    if( handle_background_event(l_ev) ) continue;

    if( l_ev.type==KeyPress ) {
      if( vertx ) {
        height = vlab_height;
        XResizeWindow( disp, wind,
                       2 * VLAB_HPAD
                       + XTextWidth( vlab_font_struct, label, i ),
                       height );
        XDrawString( disp, wind, vlab_gc, VLAB_HPAD, vlab_yoffset,
                     label, strlen(label) );
      }
      else {
        height = elab_height;
        XResizeWindow( disp, wind,
                       2 * ELAB_HPAD
                       + XTextWidth( elab_font_struct, label, i),
                       height );
        XDrawString( disp, wind, elab_gc, ELAB_HPAD, elab_yoffset,
                     label, strlen(label) );
      }
      XLookupString( (XKeyEvent *) &l_ev, lookup_string, 3, &key, 0 );
      if( (key == XK_Delete) || (key == XK_BackSpace) ) {
        if( i ) {
          i--;
        }
      }
      else if( i < LINE_LENGTH - 1 ) {
        /* cut out space,cntrl,shift,etc */
        /* space 'bombs' @ read_in_gr() due
           scanf for %s followed by %d */
        if( (key > XK_space) && (key < XK_asciitilde) ) {
          label[ i ] = lookup_string[ 0 ];
          ++i;
        }
      }
      else {
        BEEP;
      }
      label[ i ] = '\0';        /* make sure string stays terminated */
      if( vertx ) {
        height = vlab_height;
        XResizeWindow( disp, wind,
                       2 * VLAB_HPAD
                       + XTextWidth( vlab_font_struct, label, i ),
                       height );
        XDrawString( disp, wind, vlab_gc, VLAB_HPAD, vlab_yoffset,
                     label, strlen(label) );
      }
      else {
        height = elab_height;
        XResizeWindow( disp, wind,
                       2 * ELAB_HPAD
                       + XTextWidth( elab_font_struct, label, i ),
                       height );
        XDrawString( disp, wind, elab_gc, ELAB_HPAD, elab_yoffset,
                     label, strlen(label) );
      }
    } /* end, key press event */
    else if( (l_ev.type == ButtonPress) && (l_ev.BUT == LEFT_BUTTON) ) {
      if( (vx = vertex_from_window( l_ev.WIN) ) != NULL_VERTEX ) {
        hide_vertex_label( vx );
        return false;
      }
      else if( (edj = edge_from_window( l_ev.WIN )) != NULL_EDGE ) {
        hide_edge_label( edj );
        return false;
      }
      else {
        return false;
      }
    } /* end, left button press */
    else if (l_ev.type == LeaveNotify) {
      more = false;
    }
  } /* while( more )  */
  return false;
} /* edit_label ends */

/*---------------------------------------------------------------
 * Allows menu to be popped using left mouse button
 */
static bool menu_pop( void ) {
  if( eve.BUT == LEFT_BUTTON || eve.BUT == RIGHT_BUTTON ) {
    char exit_code = p_menu( eve.XC, eve.YC );
    if( exit_code == TRUE ) {
      return true;
    }
  }
  else {
    BEEP;
  }
  return false;
} /* menu_pop ends */


/* an array of function pointers -- the i-th function corresponds to the
   i-th rectangle on the side panel (all return bool to indicate whether an
   exit is required after completion */
bool (*funct[ PANEL_TOPICS ])( void ) = {
  creat_vx,
  creat_ej,
  mov,
  delet,
  edit_label,
  menu_pop
}; /* funct ends */

/*-------------------------------------------------------------
  Initialise the panels for the fixed menu.
  INPUT:
  None.
  OUTPUT:
  Global window array.
  FUNCTIONS CALLED:
  ----------------------------------------------------------------*/
void pane_init(void)
{
  unsigned long	valuemask;
  XSetWindowAttributes    attr_str;
  
  /* init the topics in the panel */
  p_top[CREATE_VERTEX] = "CREATE VERTEX";
  p_top[CREATE_EDGE] = "CREATE EDGE";
  p_top[MOVE] = "MOVE";
  p_top[DELETE] = "DELETE";
  p_top[EDIT_LABEL] = "EDIT LABEL";
  p_top[POPUP_MENU] = "POPUP MENU";
  if (direct)
    p_top[IS_DIRECTED] = "DIRECTED";
  else
    p_top[IS_DIRECTED] = "UNDIRECTED";
  
  /* create cursor for panel */
  pane_cur = XCreateFontCursor( disp, XC_hand1 );
  /* create a window */
  pane_w = XCreateSimpleWindow( disp, main_window, 0, 0, PANEL_WIDTH, WIN_HYT,
                                0, black, white );
  attr_str.bit_gravity = NorthWestGravity;
  valuemask = CWBitGravity;
  XChangeWindowAttributes( disp, pane_w, valuemask, &attr_str );
  XSelectInput( disp, pane_w, OwnerGrabButtonMask | ButtonPressMask |
                KeyPressMask | ExposureMask );
  XDefineCursor( disp, pane_w, pane_cur );

  /* Set the current selection to 'create vertex ' */
  pane_indx = CREATE_VERTEX;

  /* map the panel window */
  XMapWindow( disp, pane_w );

  /* display appropriate help message */
  show_help( PANEL_HELP, pane_indx );
}				/* pane_init ends */

void draw_panel_window( void )
     /* draws panel and highlights current selection */
{
  int i;
  int	y_pos;

  XClearWindow( disp, pane_w );

  /* write the panel topics */
  for( i = 0; i < PANEL_TOPICS; i++ ) {
    y_pos = PANEL_VPAD + (i + 1) * Y_P;
    XDrawString( disp, pane_w, rubber_gc, PANEL_HPAD, y_pos - PANEL_VPAD,
                 p_top[ i ], strlen( p_top[ i ] ) );
    XDrawLine( disp, pane_w, rubber_gc, 0, y_pos, PANEL_WIDTH, y_pos );
  }

  /* write and hilite the directed/undirected button */
  XDrawString(disp, pane_w, rubber_gc,
              PANEL_HPAD, (PANEL_TOPICS + 2) * Y_P,
              p_top[ PANEL_TOPICS ], strlen( p_top[ PANEL_TOPICS ] ) );
  HILI_PANE( PANEL_TOPICS + 1 );
  
  /* highlight current panel topic and show help message */
  HILI_PANE( pane_indx );
  show_help( PANEL_HELP, pane_indx );
  
  XFlush( disp );
} /* draw_panel_window ends */

/*---------------------------------------------------------
		Act on pane menus.
                INPUT:
                        y - the y co-ord of the window for which
			a button press was given.
                FUNCTIONS CALLED:
        ----------------------------------------------------------------*/
void act_pane(int y)
{
  int	indx;
  
  /* get the pane index */
  indx = y / Y_P;
  if ( indx >= PANEL_TOPICS ) {
    BEEP;
    return;
  }
  
  HILI_PANE( pane_indx ); /* erase current highlighting */
  HILI_PANE( indx );
  
  show_help( PANEL_HELP, indx );
  pane_indx = indx;
} /* act_pane ends */
	
/*---------------------------------------------------------------
	Queries the user for two points to form a loop edge.
	OUTPUT :Returns the knot points thru parameter 'kn'.
	FUNCTIONS CALLED : show_help().
	---------------------------------------------------------------*/
void get_2_knots( int (*kn)[2] )
{
  XEvent	l_ev;
  bool more;
  int i;
  
  show_help( OTHER_HELP, LOOP_PROMPT );
  XDefineCursor( disp, main_canvas, sel_curs );
  for( i = 0; i < 2; ++i ) { /* get the two knot points */
    more = true;
    while( more ) {
      XNextEvent( disp, &l_ev );
      if( handle_background_event( l_ev ) ) continue;
      /* choose but-press ev */ 
      if( l_ev.type == ButtonPress ) {
        /* left mouse click in main_canvas */
        if( (l_ev.BUT == LEFT_BUTTON) && (l_ev.WIN == main_canvas) ) {
          kn[ i ][ 0 ] = l_ev.XC;
          kn[ i ][ 1 ] = l_ev.YC;
          more = false;
        }
      }
    } /* while */
  } /* for i */
  
  show_help( PANEL_HELP, pane_indx );
} /* get_2_knots ends */

/*---------------------------------------------------------------
	Fills in the knot co-ords from the value of num.
	FUNCTIONS CALLED : None.
	CAN BE PUT INSIDE 'init_knot()'
	---------------------------------------------------------------*/
static void fil_in_kn( vertex vx1, vertex vx2,
                       double p_dist, double theta,
                       int (*kn)[2] ) {
  double in_3_x, in_3_y, x_inc, y_inc;
  double	rad;
  /* prevent truncation errors - add 0.5 */
  in_3_x = 0.5 + (XCORD( vx2 ) - XCORD( vx1 )) / 3;
  in_3_y = 0.5 + (YCORD( vx2 ) - YCORD( vx1 )) / 3;

  kn[ 0 ][ 0 ] = XCORD( vx1 ) + in_3_x;
  kn[ 0 ][ 1 ] = YCORD( vx1 ) + in_3_y;
  kn[ 1 ][ 0 ] = kn[ 0 ][ 0 ] + in_3_x;
  kn[ 1 ][ 1 ] = kn[ 0 ][ 1 ] + in_3_y;
		
  if( p_dist > -1.0 ) {
    rad = p_dist + RAD;
    x_inc = rad * cos( PI_2 + theta );
    y_inc = rad * sin( PI_2 + theta );
    /* add changes to the straight edge knots */
    kn[ 0 ][ 0 ] += x_inc;
    kn[ 0 ][ 1 ] += y_inc;
    kn[ 1 ][ 0 ] += x_inc;
    kn[ 1 ][ 1 ] += y_inc;
  }
} /* fil_in_kn ends */

/*---------------------------------------------------------------
	Initialises the knot points in the edge. 'vx1' must be the tail
	and vx2 the head of the edge.
	FUNCTIONS CALLED : fil_in_kn().
	---------------------------------------------------------------*/
void init_knot( vertex vx1, vertex vx2, int (*kn)[2] ) {
  bool first = true;
  edge lpt;
  vertex	lvx1, lvx2;
  double	perp_kn0, perp_kn1, max_perpd;
  double	theta1, theta0, theta2;
  int	xcv1, ycv1, xcv2, ycv2;

  max_perpd = -1.0;	/* initialization */
  /* start with the 1st edge between the vxs */
  lpt = V_FIRST( vx1 );
  while( lpt != NULL_EDGE ) {
    lvx1 = VX_1( lpt ); lvx2 = VX_2( lpt );
    if( (lvx1 == vx2) || (lvx2 == vx2) ) {
      if( lvx1 != lvx2 ) { /* avoid self_loops  */
        if( first ) {
          xcv1 = XCORD( lvx1 );
          ycv1 = YCORD( lvx1 );
          xcv2 = XCORD( lvx2 );
          ycv2 = YCORD( lvx2 );
          theta1 = atan( get_slope( xcv1, ycv1, xcv2, ycv2) );
          first = false;
        }
        /* for 'fil_in_kn()' */
        theta0 = atan( get_slope( xcv1, ycv1,
                                  KN( lpt, 0, 0 ), KN( lpt, 0, 1) ) );
        theta2 = atan( get_slope( KN( lpt, 1, 0 ), KN( lpt, 1, 1),
                                  xcv2, ycv2) );
        /* get the perp. dist between knot0 & straight edge */
        perp_kn0 = fabs( DIST( xcv1, ycv1, KN( lpt, 0, 0), KN( lpt, 0, 1) )
                         * sin( theta0 - theta1 ) );
        /* get the perp. dist between knot1 & straight edge */
        perp_kn1 = fabs( DIST( xcv2, ycv2, KN( lpt, 1, 0), KN( lpt, 1, 1) )
                         * sin( theta1-theta2 ) );
        /* take the max of the two */
        max_perpd = (perp_kn0 > max_perpd) ? perp_kn0 : max_perpd;
        max_perpd = (perp_kn1 > max_perpd) ? perp_kn1 : max_perpd;
      } /* if v1 != v2 */
    }
    /* go for the next edge */
    lpt = LNK( lpt );
  } /* while not NULL_EDGE */

  /* fill in the knots with this as the perpendicular distance */
  fil_in_kn( vx1, vx2, max_perpd, theta1, kn );
} /* init_knot ends */

void update_directed_in_panel( void ) {
  DRAW_STR( pane_w, PANEL_HPAD, (PANEL_TOPICS + 2) * Y_P,
            p_top[ PANEL_TOPICS ] );
  if( direct )
    p_top[ PANEL_TOPICS ] = "DIRECTED";
  else
    p_top[ PANEL_TOPICS ] = "UNDIRECTED";
  DRAW_STR( pane_w, PANEL_HPAD, (PANEL_TOPICS + 2) * Y_P,
            p_top[ PANEL_TOPICS ] );
}

int is_panel_window(Window w)
{
  return w == pane_w;
}

/** end of Panel.c **/

/*  [Last modified: 2007 06 28 at 19:25:58 GMT] */
